package com.bingxue.edu.management.generator.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.meta.Column;
import com.bingxue.edu.framework.common.model.PageQuery;
import com.bingxue.edu.framework.common.model.Pagination;
import com.bingxue.edu.framework.common.util.CheckUtils;
import com.bingxue.edu.management.generator.config.GeneratorProperties;
import com.bingxue.edu.management.generator.enums.DatabaseType;
import com.bingxue.edu.management.generator.mapper.FieldConfigMapper;
import com.bingxue.edu.management.generator.mapper.GenConfigMapper;
import com.bingxue.edu.management.generator.model.entity.FieldConfig;
import com.bingxue.edu.management.generator.model.entity.GenConfig;
import com.bingxue.edu.management.generator.model.query.TableQuery;
import com.bingxue.edu.management.generator.model.req.GenConfigReq;
import com.bingxue.edu.management.generator.model.resp.TableResp;
import com.bingxue.edu.management.generator.service.GeneratorService;
import com.bingxue.edu.management.generator.service.TableMetaService;
import com.bingxue.edu.management.generator.util.MetaUtils;
import com.bingxue.edu.management.generator.util.Table;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 代码生成业务实现
 *
 * @author panbo
 * @since 2024/7/7
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TableMetaServiceImpl implements TableMetaService {

    private final DataSource dataSource;
    private final GeneratorProperties generatorProperties;
    private final FieldConfigMapper fieldConfigMapper;
    private final GenConfigMapper genConfigMapper;

    @Override
    public Pagination<TableResp> pageTable(TableQuery query, PageQuery pageQuery) throws SQLException {
        List<Table> tableList = MetaUtils.getTables(dataSource);
        String tableName = query.getTableName();
        if (StrUtil.isNotBlank(tableName)) {
            tableList.removeIf(table -> !StrUtil.containsAnyIgnoreCase(table.getTableName(), tableName));
        }
        tableList.removeIf(table -> StrUtil.equalsAnyIgnoreCase(table.getTableName(), generatorProperties.getExcludeTables()));
        CollUtil.sort(tableList, Comparator.comparing(Table::getCreateTime)
            .thenComparing(table -> Optional.ofNullable(table.getUpdateTime()).orElse(table.getCreateTime()))
            .reversed());
        List<TableResp> tableRespList = BeanUtil.copyToList(tableList, TableResp.class);
        Pagination<TableResp> pagination = Pagination.of(pageQuery.getPage(), pageQuery.getSize(), tableRespList);
        for (TableResp tableResp : pagination.getRecords()) {
            QueryWrapper queryWrapper = QueryWrapper.create().where(GenConfig::getTableName).eq(tableResp.getTableName());
            long count = genConfigMapper.selectCountByQuery(queryWrapper);
            tableResp.setIsConfiged(count > 0);
        }
        return pagination;
    }

    @Override
    public GenConfig getGenConfig(String tableName) throws SQLException {
        GenConfig genConfig = genConfigMapper.selectOneById(tableName);
        if (null == genConfig) {
            genConfig = new GenConfig(tableName);
            // 默认包名（当前包名）
            String packageName = ClassUtil.getPackage(GeneratorService.class);
            genConfig.setPackageName(StrUtil.subBefore(packageName, StrUtil.DOT, true));
            // 默认业务名（表注释）
            List<Table> tableList = MetaUtils.getTables(dataSource, tableName);
            if (CollUtil.isNotEmpty(tableList)) {
                Table table = tableList.get(0);
                genConfig.setBusinessName(StrUtil.replace(table.getComment(), "表", StrUtil.EMPTY));
            }
            // 默认作者名称（上次保存使用的作者名称）
            GenConfig lastGenConfig = genConfigMapper.selectOneByQuery(QueryWrapper.create().orderBy(GenConfig::getCreateTime).desc());
            if (null != lastGenConfig) {
                genConfig.setAuthor(lastGenConfig.getAuthor());
            } else {
                genConfig.setAuthor(System.getProperty("user.name"));
            }
            // 默认表前缀（sys_user -> sys_）
            int underLineIndex = StrUtil.indexOf(tableName, StrUtil.C_UNDERLINE);
            if (underLineIndex != -1) {
                genConfig.setTablePrefix(StrUtil.subPre(tableName, underLineIndex + 1));
            }
        }
        return genConfig;
    }

    @Override
    public List<FieldConfig> listFieldConfig(String tableName, Boolean requireSync) {
        List<FieldConfig> fieldConfigList = listFieldConfigByTableName(tableName);
        if (CollUtil.isNotEmpty(fieldConfigList) && !requireSync) {
            return fieldConfigList;
        }
        List<FieldConfig> latestFieldConfigList = new ArrayList<>();
        // 获取最新数据表列信息
        Collection<Column> columnList = MetaUtils.getColumns(dataSource, tableName);
        // 新增或更新字段配置
        Map<String, FieldConfig> fieldConfigMap = fieldConfigList.stream().collect(Collectors.toMap(FieldConfig::getColumnName, item -> item));
        int i = 1;
        for (Column column : columnList) {
            FieldConfig fieldConfig = Optional.ofNullable(fieldConfigMap.get(column.getName())).orElseGet(() -> new FieldConfig(column));
            // 更新已有字段配置
            if (fieldConfig.getCreateTime() != null) {
                String columnType = StrUtil.splitToArray(column.getTypeName(), StrUtil.SPACE)[0].toLowerCase();
                fieldConfig.setColumnType(columnType);
                fieldConfig.setColumnSize(Convert.toStr(column.getSize()));
            }
            String fieldType = convertToJavaType(fieldConfig);
            fieldConfig.setFieldType(fieldType);
            fieldConfig.setFieldSort(i++);
            latestFieldConfigList.add(fieldConfig);
        }
        return latestFieldConfigList;
    }

    @Override
    public GenConfig getGenConfigById(String tableName) {
        return genConfigMapper.selectOneById(tableName);
    }

    @Override
    public List<FieldConfig> listFieldConfigByTableName(String tableName) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select()
                .where(FieldConfig::getTableName).eq(tableName)
                .orderBy(FieldConfig::getFieldSort).asc();
        return fieldConfigMapper.selectListByQuery(queryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveConfig(GenConfigReq req, String tableName) {
        // 保存字段配置
        QueryWrapper queryWrapper = QueryWrapper.create().where(FieldConfig::getTableName).eq(tableName);
        fieldConfigMapper.deleteByQuery(queryWrapper);
        List<FieldConfig> fieldConfigList = req.getFieldConfigs();
        for (FieldConfig fieldConfig : fieldConfigList) {
            if (Boolean.TRUE.equals(fieldConfig.getShowInForm())) {
                CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
            } else {
                // 在表单中不显示，不需要设置必填
                fieldConfig.setIsRequired(false);
            }
            if (Boolean.TRUE.equals(fieldConfig.getShowInQuery())) {
                CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
                CheckUtils.throwIfNull(fieldConfig.getQueryType(), "字段 [{}] 的查询方式不能为空", fieldConfig.getFieldName());
            } else {
                // 在查询中不显示，不需要设置查询方式
                fieldConfig.setQueryType(null);
            }
            // 既不在表单也不在查询中显示，不需要设置表单类型
            if (Boolean.FALSE.equals(fieldConfig.getShowInForm()) && Boolean.FALSE.equals(fieldConfig
                .getShowInQuery())) {
                fieldConfig.setFormType(null);
            }
            fieldConfig.setTableName(tableName);
            fieldConfig.setCreateTime(LocalDateTime.now());
        }
        fieldConfigMapper.insertBatch(fieldConfigList);
        // 保存或更新生成配置信息
        GenConfig newGenConfig = req.getGenConfig();
        GenConfig oldGenConfig = genConfigMapper.selectOneById(tableName);
        if (null != oldGenConfig) {
            BeanUtil.copyProperties(newGenConfig, oldGenConfig);
            oldGenConfig.setUpdateTime(LocalDateTime.now());
            genConfigMapper.update(oldGenConfig);
        } else {
            newGenConfig.setCreateTime(LocalDateTime.now());
            newGenConfig.setUpdateTime(LocalDateTime.now());
            genConfigMapper.insert(newGenConfig);
        }
    }

    /**
     * 将数据库类型转换为java类型
     * @param fieldConfig 字段配置
     * @return java类型
     */
    private String convertToJavaType(FieldConfig fieldConfig) {
        String columnType = fieldConfig.getColumnType();
        String columnComment = fieldConfig.getComment();
        String columnName = fieldConfig.getColumnName();

        if (columnName.equals("status")) {
            // 特殊处理status枚举
            return "CommonStatusEnum";
        } else if (columnComment.startsWith("enum-")) {
            // 特殊处理枚举类
            return StrUtil.upperFirst(StrUtil.toCamelCase(columnName)) + "Enum";
        }
        // 获取数据库对应的类型映射配置
        DatabaseType databaseType = MetaUtils.getDatabaseType(dataSource);
        Map<String, List<String>> typeMappingMap = generatorProperties.getTypeMappings().get(databaseType);
        CheckUtils.throwIfEmpty(typeMappingMap, "请先配置对应数据库的类型映射");
        Set<Map.Entry<String, List<String>>> typeMappingEntrySet = typeMappingMap.entrySet();
        return typeMappingEntrySet.stream()
                .filter(entry -> entry.getValue().contains(columnType))
                .map(Map.Entry::getKey)
                .findFirst()
                .orElse(null);
    }
}
